package com.corticon.client.validation;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.annotation.Resource;

import org.hibernate.validator.ClassValidator;
import org.hibernate.validator.InvalidValue;

import org.springframework.validation.Errors;

/**
 * 
 * @author Heweiya
 */
public class GenericHibernateValidator<T> extends GenericValidator {

	private ClassValidator<T> hibernateClassValidator = new ClassValidator<T>(super.getValidateObjectClass());
	@Resource
	protected ValidationMessageFormatter validationMessageFormatter;

	@Override
	public void validate(Object object, Errors errors) {
		hibernateValidate(object, errors);
		complexValidate(object, errors);
	}

	/**
	 * This method invoke the true validation logic, then convert the format of
	 * the error messages into Map type. Support i18n.
	 * 
	 * @param object
	 *            The object be validated.
	 * @param errors
	 * @param locale
	 *            Location info, used for i18n.
	 * @return
	 */
	public Map<String, List<String>> invokeValidate(Object object, Errors errors, Locale locale) {
		Map<String, List<String>> errorMessages = new HashMap<String, List<String>>();

		// Invoke the true validate method.
		validate(object, errors);
		// Convert the error messages from org.springframework.validation.Errors
		// to Map.
		errorMessages = validationMessageFormatter.convertErrorsMessageToMap(object, errors, locale);
		return errorMessages;
	}

	/**
	 * This method use {@link ClassValidator} to validate a model with hibernate
	 * validator annotations. And reject the error messages into {@link Errors}.
	 * 
	 * @param object:
	 *            The object should be validated.
	 * @param errors:
	 *            The errors message.
	 */
	private void hibernateValidate(Object object, Errors errors) {
		T hibernateValidateObject = (T) object;
		InvalidValue[] validateMessages = null;
		validateMessages = hibernateClassValidator.getInvalidValues(hibernateValidateObject);

		if (validateMessages != null && validateMessages.length > 0) {
			for (InvalidValue invalidValue : validateMessages) {
				errors.rejectValue(invalidValue.getPropertyPath(), invalidValue.getMessage(), "default message");
			}
		}
	}

	/**
	 * The template method you should implement. Hibernate Validator can
	 * complete the most of validation works. But some complex validations
	 * should be implemented programmingly.
	 * 
	 * @param object
	 * @param errors
	 */
	protected void complexValidate(Object object, Errors errors) {
	}

	/**
	 * The method is used to validate the simple string like a name. The numbers
	 * and alphabets and '_' are valid.
	 * 
	 * @param errors
	 * @param validateString
	 * @param field
	 * @param messageCode
	 */
	protected void simpleStringValidate(Errors errors, String validateString, String field, String messageCode) {
		List<Character> excludedChars = new ArrayList<Character>();
		excludedChars.add('_');
        excludedChars.add('/');
		commonCharactersValidate(validateString, excludedChars, errors, field, messageCode, null, "default message");
	}

	protected void commonCharactersValidate(String characters, List<Character> excludedChars, Errors errors, String fieldName, String errorCode, Object[] errorArgs, String defaultMessage) {

		boolean isValid = true;

		if (isValid) {
			isValid = checkInvalidCharacters(characters, ' ', '/', excludedChars) && checkInvalidCharacters(characters, ':', '@', excludedChars)
					&& checkInvalidCharacters(characters, '[', '`', excludedChars) && checkInvalidCharacters(characters, '{', '~', excludedChars);
		}

		// If the password is invalid, add the error message.
		if (!isValid) {
			errors.rejectValue(fieldName, errorCode, errorArgs, defaultMessage);
		}
	}

	protected boolean checkValidCharacters(String value, char asciiBegin, char asciiEnd, List<Character> excludedChars) {
		boolean isValidPassword = false;

		int i = asciiBegin;

		while (!isValidPassword) {

			if (excludedChars != null && excludedChars.contains((char) i)) {
				i++;
				continue;
			}

			isValidPassword = value.indexOf((char) i) > -1 ? true : false;

			i++;
			if (i > asciiEnd) {
				break;
			}
		}

		return isValidPassword;
	}

	protected boolean checkInvalidCharacters(String value, char asciiBegin, char asciiEnd, List<Character> excludedChars) {
		boolean isValidPassword = true;

		int i = asciiBegin;

		while (isValidPassword) {

			if (excludedChars != null && excludedChars.contains((char) i)) {
				i++;
				continue;
			}

			isValidPassword = value.indexOf((char) i) > -1 ? false : true;

			i++;
			if (i > asciiEnd) {
				break;
			}
		}

		return isValidPassword;
	}

	public void setValidationMessageFormatter(ValidationMessageFormatter validationMessageFormatter) {
		this.validationMessageFormatter = validationMessageFormatter;
	}
}
